package config

import (
	_ "embed"
	"fmt"
	"gmap-backend/internal/config/secret"
	"gmap-backend/internal/ipc/namedpipe"
	"gmap-backend/internal/util/commonutil"
	"io"
	"log"
	"os"
	"path/filepath"
	"syscall"
	"time"
)

type SecretConfig struct {
	Sm4Key string
}

type PathConfig struct {
	ExternalDir    string
	WorkDir        string
	CacheDir       string
	MapsDir        string
	LogPath        string
	ServerInfoPath string
}

type ServerConfig struct {
	PipeName    string
	LogFile     *os.File
	ReadySymbol string
}

type Win32LibConfig struct {
	DllPath                 string
	DllObj                  *syscall.DLL
	LoadIconProc            *syscall.Proc
	LoadIconByIndProc       *syscall.Proc
	GetWorkAreaProc         *syscall.Proc
	GetClipFiles            *syscall.Proc
	GetClipTxt              *syscall.Proc
	ReleaseStr              *syscall.Proc
	SaveClipImgToFile       *syscall.Proc
	ClipContainsImg         *syscall.Proc
	SaveImgBytesToClipboard *syscall.Proc
}

type AppConfig struct {
	PathConfig     *PathConfig
	SecretConfig   *SecretConfig
	ServerConfig   *ServerConfig
	Win32LibConfig *Win32LibConfig
}

func LoadConfig(readySymbol string) *AppConfig {
	exePath, err := os.Executable()
	if err != nil {
		panic("项目配置信息加载失败")
	}

	externalDir := filepath.Dir(exePath)
	appDir := filepath.Dir(externalDir)
	workDir := filepath.Join(appDir, "work")
	cacheDir := filepath.Join(appDir, "cache")
	mapsDir := filepath.Join(appDir, "gmaps")
	now := time.Now()
	logFn := fmt.Sprintf("gmap_backend_%d_%02d_%02d.log", now.Year(), now.Month(), now.Day())
	logPath := filepath.Join(workDir, logFn)
	mkdirIfNeed(workDir, cacheDir, mapsDir)

	// 把log的输出指向控制台和日志文件
	logFile, err := os.OpenFile(logPath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	commonutil.ThrowIfNeed(err)
	writer := io.MultiWriter(os.Stdout, logFile)
	log.SetOutput(writer)

	pathConf := PathConfig{
		ExternalDir:    externalDir,
		WorkDir:        workDir,
		CacheDir:       cacheDir,
		MapsDir:        mapsDir,
		LogPath:        logPath,
		ServerInfoPath: filepath.Join(workDir, "backend_info"),
	}

	secretConfig := SecretConfig{
		Sm4Key: secret.DEFAULT_SM4_KEY,
	}

	serverConfig := ServerConfig{
		PipeName:    getRandomNamedPipe(),
		LogFile:     logFile,
		ReadySymbol: readySymbol,
	}

	dllPath := filepath.Join(externalDir, "gmap_backend_assist.dll")
	dll, err := syscall.LoadDLL(dllPath)
	commonutil.ThrowIfNeed(err)
	loadIconProc, err := dll.FindProc("extractFileIcon")
	commonutil.ThrowIfNeed(err)
	loadIconByIndProc, err := dll.FindProc("extractFileIconByIndex")
	commonutil.ThrowIfNeed(err)
	getWordAreaProc, err := dll.FindProc("getWorkArea")
	commonutil.ThrowIfNeed(err)
	getClipFiles, err := dll.FindProc("getClipboardFileItems")
	commonutil.ThrowIfNeed(err)
	getClipTxt, err := dll.FindProc("getClipboardTxt")
	commonutil.ThrowIfNeed(err)
	releaseStr, err := dll.FindProc("releaseStr")
	commonutil.ThrowIfNeed(err)
	saveClipImgToFile, err := dll.FindProc("saveClipboardImgToFile")
	commonutil.ThrowIfNeed(err)
	clipContainsImg, err := dll.FindProc("clipboardContainsImg")
	commonutil.ThrowIfNeed(err)
	saveImgBytesToClipboard, err := dll.FindProc("saveImgBytesToClipboard")
	commonutil.ThrowIfNeed(err)

	win32LibConfig := Win32LibConfig{
		DllPath:                 dllPath,
		DllObj:                  dll,
		LoadIconProc:            loadIconProc,
		LoadIconByIndProc:       loadIconByIndProc,
		GetWorkAreaProc:         getWordAreaProc,
		GetClipFiles:            getClipFiles,
		GetClipTxt:              getClipTxt,
		ReleaseStr:              releaseStr,
		SaveClipImgToFile:       saveClipImgToFile,
		ClipContainsImg:         clipContainsImg,
		SaveImgBytesToClipboard: saveImgBytesToClipboard,
	}

	conf := AppConfig{
		PathConfig:     &pathConf,
		SecretConfig:   &secretConfig,
		ServerConfig:   &serverConfig,
		Win32LibConfig: &win32LibConfig,
	}
	return &conf
}

func mkdirIfNeed(dirs ...string) {
	if len(dirs) == 0 {
		return
	}
	for _, dir := range dirs {
		_, err := os.Stat(dir)
		if err != nil && os.IsNotExist(err) {
			os.MkdirAll(dir, 0666)
		}
	}
}

func getRandomNamedPipe() string {
	return namedpipe.CreateFullPipeName(fmt.Sprintf("gmap_%v", time.Now().UnixNano()))
}
